package ija.model.basis;

import ija.model.figures.Piece;
import ija.model.figures.Queen;
import ija.model.types.FigureColor;
import ija.model.types.GameMoves;
import ija.xls.notation.DeskCoordinate;
import ija.xls.notation.types.GameOperation;

import java.io.Serializable;
import java.util.*;

/**
 * Třída reprezentuje hrací šachovnici. Je realizovaná jako pole objektů
 * Position. Skrze šachovnici se přistupuje ke všem pozicím a figurkám v dané
 * hře.
 * 
 * @author Radek Necas, xnecas18
 */
public class Desk implements Iterable<Position>, Serializable {
	public static final int NORMAL_SIZE = 8;

	/** Dvourozměrné pole pozic[řádek][sloupec] */
	protected Position[][] board;
	protected int dimension;
        protected LinkedList<Figure> removedFigures;

	// /////////////////////////////////////////////////
	// ---- Pozadovane metody dle API specifikace ---//
	// /////////////////////////////////////////////////

	/**
	 * Konstruktor vytváří šachovnici daných rozměrů.
	 * 
	 * @param dim
	 *            rozměry šachovnice (šířka totožná s výškou).
	 */
	public Desk(int dim) {
                this.removedFigures = new LinkedList<>();
		this.setDimension(dim);
		this.board = new Position[dim][dim];
		for (int i = 0; i < dim; i++) {
			for (int j = 0; j < dim; j++) {
				this.board[i][j] = new Position(this, Desk.unmapColumn(j), Desk.unmapRow(i));
			}
		}
	}


	/**
	 * Bezparametrický konstruktor vytváří šachovnici standardních rozměrů
	 * (8x8).
	 */
	public Desk() {
		this(NORMAL_SIZE);
	}

        
        /**
         * Metoda nastavi dimenzi pro desku
         * @param dimension dimenze desky
         */
        public void setDimension(int dimension) {
		this.dimension = dimension;
	}
        
        /**
         * Metoda vlozi odstranenou figurku do zasobniku.
         * Slouzi pro umozneni provest tah zpet.
         * @param f figurka, ktera bude vlozena do zasobniku. 
         */
        public void pushRemovedFigure(Figure f) {
            removedFigures.push(f);
        }
        
        /**
         * Metoda vybere figurku z vrcholu zasobniku a odstrani ji.
         * @return figurka z vrcholu zasobniku nebo null, pokud je prazdny
         */
        public Figure popRemovedFigure() {
            return removedFigures.poll();
        }
        
        /**
         * Metoda vycisti zasobnik vyhozenych figurek.
         */
        public void clearRemovedFigure() {
            this.removedFigures.clear();
        }
        
	/**
	 * Metoda pro získání pozice.
	 * 
	 * @param c
	 *            identifikátor sloupce
	 * @param r
	 *            identifikátor řádku
	 * @return pozice na daném řádku a sloupci
	 * 
	 * @see ija.model.basis.Position
	 */
	public Position getPositionAt(char c, int r) {
		if (this.isInside(c, r)) {
			return this.board[Desk.mapRow(r)][Desk.mapColumn(c)];
		}

		return null;
	}

	public Position getPositionAt(DeskCoordinate coord) {
		return this.getPositionAt(coord.getColumn(), coord.getRow());
	}

	/**
	 * Metoda pro ziskani figurky
	 * 
	 * @param c
	 *            identifikátor sloupce
	 * @param r
	 *            identifikátor řádku
	 * @return figurku na daném řádku a sloupci
	 * 
	 * @see ija.model.basis.Figure
	 */
	public Figure getFigureAt(char c, int r) {
		if (this.isInside(c, r)) {
			return this.board[Desk.mapRow(r)][Desk.mapColumn(c)].getFigure();
		}

		return null;
	}

	public Figure getFigureAt(DeskCoordinate coord) {
		return this.getFigureAt(coord.getColumn(), coord.getRow());
	}

	/**
	 * Metoda pro ziskani rozměrů šachovnice.
	 * 
	 * @return dimenzi šachovnice. Počet řádků je vždy stejný jako počet
	 *         sloupců.
	 */
	public int getDimension() {
		return this.dimension;
	}

	/**
	 * Test zda jsou dané souřadnice uvnitř šachovnice.
	 * 
	 * @param c
	 *            identifikátor sloupce
	 * @param r
	 *            identifikátor řádku
	 * @return true pokud jsou souřadnice uvnitř šachovnice, jinak false.
	 */
	public boolean isInside(char c, int r) {
		if (Desk.mapRow(r) < 0 || Desk.mapColumn(c) < 0) {
			return false;
		}

		if ((Desk.mapRow(r) < this.getDimension()) && (Desk.mapColumn(c) < this.getDimension())) {
			return true;
		}
		return false;
	}

	/**
	 * Test zda je daná pozice uvnitř šachovnice. Pozice nemusí být v
	 * šachovnici, testují se pouze souřadnice pozice.
	 * 
	 * @param p
	 *            testovaná pozice
	 * @return true pokud je pozice v šachovnici, jinak false.
	 */
	public boolean isInside(Position p) {
		return (this.isInside(p.getColumnChar(), p.getRowNumber()));
	}

	/**
	 * Metoda pro získání všech figurek dané barvy
	 * 
	 * @param c
	 *            barva figurek, které chceme získat
	 * @return list se všemi figurkami dané barvy
	 */
	public List<Figure> getAllFigures(FigureColor c) {
		List<Figure> fig = new ArrayList<>();
		Figure f;

		for (int row = 1; row <= this.dimension; row++) {
			for (int col = 0; col < this.dimension; col++) {
				f = this.getFigureAt((char) (col + 'a'), row);
				if (f != null) {
					if (c == FigureColor.UNKNOWN || c == f.getColor()) {
						fig.add(f);
					}
				}
			}
		}

		return fig;
	}

	/**
	 * Metoda pro získání všech figurek
	 * 
	 * @return list se všemi figurkami
	 */
	public List<Figure> getAllFigures() {
		return this.getAllFigures(FigureColor.UNKNOWN);
	}

	public List<GameMoves> getAllMoves(FigureColor fc) {
		List<Figure> lF = this.getAllFigures(fc);
		if (lF == null) {
			return null;
		}
		List<GameMoves> lGMAll = new ArrayList<GameMoves>();
		List<GameMoves> lGMJumps = new ArrayList<GameMoves>();
		for (Figure f : lF) {
			GameMoves fr;
			fr = f.getAllMoves();
			if (fr != null) {
				lGMAll.add(fr);
			}
		}
		if (lGMAll.isEmpty()) {
			return null;
		}

		for (GameMoves gm : lGMAll) {
			if (gm.getOperationType() == GameOperation.JUMP) {
				lGMJumps.add(gm);
			}
		}

		if (!lGMJumps.isEmpty()) {
			return lGMJumps;
		}

		return lGMAll;
	}

	/**
	 * Metoda provadi hlubokou kopii sachovnice z jine sachovnice. Vytvari se
	 * nove objekty, nekopiruji se odkazy.
	 * 
	 * @param copyDesk
	 *            hraci deska ke zkopirovani
	 */
        /*
	public void setDeskOtherDesk(Desk copyDesk) {
		Figure f;
		Position p;
		int r;
		char c;
		for (Position copyP : copyDesk) {
			c = copyP.getColumnChar();
			r = copyP.getRowNumber();
			if ((f = this.getFigureAt(c, r)) != null) {
				p = this.getPositionAt(c, r);
				if (f instanceof Piece) {
					p.putFigure(new Piece(p, (Piece) f));
				} else if (f instanceof Queen) {
					p.putFigure(new Queen(p, (Queen) f));
				}
			}
		}
	}*/
        
        public void clear() {
            for(Position p: this) {
                if(p.getFigure() != null) {
                    p.removeFigure();
                }
            }
        }

	/**
	 * Vypíše na stdout obsah šachovnice.
	 */
	public void print() {
		for (int i = 0; i < this.dimension; i++) {
			for (int j = 0; j < this.dimension; j++) {
				this.board[i][j].print();
				System.out.println();
			}
		}
	}

	/**
	 * Vypíše na stdout obsah šachovnice, avšak pouze obsazená pole (na kterých
	 * je figurka).
	 */
	public void printOccupy() {
		for (int i = 0; i < this.dimension; i++) {
			for (int j = 0; j < this.dimension; j++) {
				if (this.board[i][j].getFigure() != null) {
					this.board[i][j].print();
					System.out.println();
				}
			}
		}
	}

	@Override
	public Iterator iterator() {
		return new DeskIterator();
	}

	// //////////////////////////////////////////////////////////////
	// ----------- Soukrome metody ------------------------------//
	// //////////////////////////////////////////////////////////////

	/**
	 * Mapuje pismenne oznaceni sloupce na odpovidajici cislo sloupce v matici
	 * board
	 */
	private static int mapColumn(char c) {
		return (c - 'a');
	}

	/** Mapuje ciselne oznaceni radku na odpovidajici cislo radku v matici board */
	private static int mapRow(int r) {
		return (r - 1);
	}

	/** Mapuje zpetne cislo sloupce matice board na pismenne oznaceni sloupce */
	public static char unmapColumn(int c) {
		return ((char) ('a' + c));
	}

	/** Mapuje zpetne cislo radku matice board na pismenne oznaceni radku */
	public static int unmapRow(int r) {
		return (r + 1);
	}

	// //////////////////////////////////////////////////////////////
	// ----------- Vnorena trida - Iterator --------------------//
	// //////////////////////////////////////////////////////////////

	public class DeskIterator implements Iterator<Position> {
		private int row;
		private int col;

		public DeskIterator() {
			this.row = 0;
			this.col = 0;
		}

		@Override
		public boolean hasNext() {
			if ((this.row < Desk.this.dimension) && (this.col < Desk.this.dimension)) {
				return true;
			}

			return false;
		}

		/**
		 * Metoda ziskava pozice pruchodem po radcich
		 */
		@Override
		public Position next() {
			// Ziskani pozice pro vraceni
			Position p;
			if ((this.row < Desk.this.dimension) && (this.col < Desk.this.dimension)) {
				p = Desk.this.board[this.row][this.col];
			} else {
				throw new NoSuchElementException();
			}

			int r = this.row + 1, c = this.col + 1;

			// Pokud se muzu posunout v sloupci udelam tak
			if (c < Desk.this.dimension) {
				this.col++;
			} else { // Pokud ne musim se posunout v radku
				if (r < Desk.this.dimension) {
					this.col = 0;
					this.row++;
				} else { // Pokud uz jsem za poslednim radkem, nastavim mezni
							// hodnoty, aby hasNext vratilo false
					this.row++;
					this.col = this.row;
				}
			}

			return p;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException("Not supported yet.");
		}
	}

}